Handoff Contacts — Collaborator Links & per-handoff Grants
Spec update: QR is now only for establishing a first-time Collaborator Link — a long-lived E2E trust binding that proves identity and gives a delivery address, and grants zero session access. Starting a handoff = picking a contact; the daemon delivers the offer over the existing link plus a content-free push. Per-handoff access is a separate temporary Grant (contact + handoff + session + scope + expiry). Links are one-directional (A→B) or mutual (⇄). “Connected” means the trust binding is valid — never presence. Status colour law unchanged: waiting = neutral · in-progress = terracotta · returned/completed = green · recalled/expired = amber · declined/cancelled = grey · red reserved for Recall, Revoke & Remove.
Frame 1 · Recipient picker390×844 · dark · draft sheet, page 2 — replaces the free-text recipient row
Choose recipient
Search collaborators…
Recent
FFrank Osei⇄2 handoffs · last week
MMei Tanaka⇄5 handoffs · 3 days ago
All contacts5
AAiko Sato→connected 12d agono daemon
DDev Rao⇄connected 2mo ago · 1 handoff
FFrank Osei⇄connected 6w ago · 2 handoffs
MMei Tanaka⇄connected 4mo ago · 5 handoffs
SSofia Lindqvist→connected 5mo agono daemon
Connect a new colleague…
one-time QR · establishes trust only
Tapping the Recipient row pushes this page inside the same sheet — back chevron, no new modal. Tap a row = select and pop straight back; there is no confirm step, so rows are 60pt and the chevron is decoration. Direction glyphs are muted mono, never accent: ⇄ mutual, → you can send to them only. no daemon is a quiet fact, not a warning — they can still receive and review a handoff from a phone. The connect row sits in the footer, separated by the hairline: it is the only surface in the whole flow that still shows a QR.
Frame 1b · Picker — no contacts yet390×520 · dark · first-run empty
Choose recipient
You haven't connected anyone yet. Connecting is a one-time QR scan that proves who they are — it shares no sessions.
Empty state carries the whole mental model in one sentence, then a single primary. No secondary “skip” — there is no handoff without a contact.
The full conversation history of this session will be visible — including anything you pasted into it.
Handoff briefautoEdit
Goal
Split AuthInterceptor so token refresh is testable without a live socket.
Ask
Review the concurrency around the refresh mutex before I open the PR.
Nothing else moved: session card, Role segment, expiry pills, boundary card and brief are the shipped components, unchanged. Two deltas only — the recipient field now holds a contact chip (28pt avatar + label + direction glyph, whole chip tappable, Change re-opens Frame 1), and the primary reads Send to Frank: the offer is delivered over the existing link, so there is no artefact for the user to carry anywhere. Expiry now labels the Grant, not the link — hence the hint. The link outlives every handoff; the grant dies on return, decline or timeout.
Frame 3 · Connect a colleague — initiator390×800 · dark · the only QR left in the product
9:41
Connect a colleague
K4TP-9BRX
ticket valid 10:00
This QR only establishes a trusted connection. It shares no sessions, folders or code.
waiting for scan…
A collaborator ticket, not an invite: it carries a public key and a delivery address, no session id and no scope. Short code is the fallback for anyone who can't scan — same 8-char alphabet as the shipped invite. “waiting for scan…” uses the neutral dot; nothing is running, and it must not read as presence. Ticket TTL is short (10 min) because it is a one-shot introduction, unlike the link it creates.
Frame 3b · Connected — return to the draft390×520 · dark · success sub-state of Frame 3
9:41
Connect a colleague
A
Aiko SatoConnected
Aiko can now receive handoffs from you. They still see nothing until you send one and they accept it.
“Connected” is green because the binding succeeded — it is a completion, not a presence light. The primary returns to the interrupted draft with Aiko pre-selected; Done exists for people who came from Settings.
Frame 4 · Confirm connection — recipient390×844 · dark · trust screen, same seriousness as accept
9:41
Confirm connectionTRUST
PPanda · MacBook Pro
first connection·scanned just now
Safety fingerprint
tiger — brick — mango — void
amber — pilot — nine — harbor
Compare these words with Panda in person or on a call. If they don't match, cancel — someone else may be in the middle.
Panda → youPanda can send you handoffs. You can't see anything of theirs until you accept one.
No access is granted nowno sessions · no folders · no code
The fingerprint is the whole point of the screen, so it gets the largest type on it (16px mono, two lines of four words) on a raised card. Word groups beat hex: people read them aloud correctly. Direction is stated in the second person and includes the negative half — the recipient's first question is always “what can they see of mine?”. Cancel is a plain secondary; refusing a connection is not dangerous. Hit targets: both footer buttons 48pt, spaced 8pt.
Frame 5 · Settings ▸ Collaborators390×760 · dark · a management surface, not a feed
9:41
Collaborators
Search collaborators…
Connected4
FFrank Osei⇄connected 12d ago · 3 handoffs
Remove
MMei Tanaka⇄connected 4mo ago · 5 handoffs
AAiko Sato→connected 2m ago · no handoffs yetno daemon
DDev Rao⇄connected 2mo ago · 1 handoff
Removed
SSofia Lindqvistlink revoked 8d ago · 2 past handoffsremoved
Reached from Settings; the header QR glyph is the second (and last) entry to Frame 3. Rows carry facts only — when the link was made and how many handoffs have run over it. No avatars-with-status, no “last seen”: we don't have presence and won't imply it. Swipe reveals a single Remove action — the only danger colour on the screen. Removed contacts fall to a terminal group at 45%: past handoffs still reference them, so the row can't just disappear.
Frame 5b · Collaborators — empty390×340 · dark
Collaborators
No collaborators yet. A connection is a one-time QR scan; it proves who someone is and gives your daemon a way to reach them.
Same empty grammar as the shipped Handoffs empty state — glyph, one sentence, ghost button.
Frame 6 · Contact detail390×1000 scrolling · dark · viewer has no daemon of their own
9:41
Collaborator
P
Panda→
MacBook Pro · connected 12d ago
Safety fingerprint
tiger — brick — mango — void
amber — pilot — nine — harbor
Direction
Panda → youPanda can send you handoffs. You can't send them one.
Complete the reverse linkneeds a daemon on your computer
Handoffs with Panda3
Refactor auth module18 min · 5 findings · todayCompleted
Fix flaky socket testnever opened · last weekExpired
Release notes 1.63 weeks agoDeclined
Remove connectionPanda can no longer reach you; past handoffs stay in history.
Shown from the recipient's side, where the link is one-directional and the reverse leg is disabled with a reason rather than hidden — the mono hint tells them exactly what's missing. History reuses the shipped status chips verbatim, so the colour law reads identically here and in Session info. Remove is the last row, states its consequence in one line, and is the only red on the screen. No confirmation dialog is drawn here — it exists, and it repeats this same sentence.
Frame 7a · Offer on the home surface390×844 · dark · content-free push, then the card
9:41
cc-pocket
You have a new handoff offer. Open the app to see the details.
now
Handoffs
PPanda asked you to review a session
Reviewread-onlycc-pocket
expires in 23:57:04
Earlier
MMei · Fix flaky socket testyesterday · you approvedCompleted
DDev · Gateway model presets3 days agoDeclined
The push is deliberately content-free — “you have a new handoff offer”, no project name, no sender detail — because notification payloads leave the E2E envelope. Everything specific arrives over the link and renders in-app. Two pending offers stack (the peek behind); three or more collapse into a counted group. View is terracotta: this is a needs-you moment. It opens the shipped accept preview unchanged — the card is a doorway, not a second trust screen, so it never lists scope details.
Frame 7b · Notification-tap landing390×620 · dark · same card, focused containment
9:41
Handoff offerNEW
PPanda asked you to review a session
Reviewread-onlycc-pocket
expires in 23:57:04
Viewing shows you what Panda is sharing. Nothing opens until you accept on the next screen.
Tapping the push lands on the offer, never straight in the session — the accept preview must always be crossed on purpose. Same card, bigger buttons, plus one line explaining that View is safe.
Frame 8 · WAITING banner — delta390×272 · dark · banner region only; rest of the screen unchanged
Offer sent to Frank · Review · expires in 24h
Waiting for Frank to accept
offer sent · Frank notified · expires 23:57:04
Input locked during handoff
Same card, same neutral dot, same 44pt buttons — only the strings and the left action change. The mono line drops the short code (there is no code any more) and states delivery instead: offer sent · Frank notified. “Notified” is honest about what we know — the push was accepted by the service; it does not claim Frank saw it, and never claims he is online. Resend notify re-pings over the same link and rate-limits to once a minute; it does not mint anything new. Recall keeps its danger tint and stays the rightmost, destructive option.
Desktop — 1200ptSame tokens, same status law. The picker becomes an anchored popover; Collaborators becomes a two-column settings pane.
Frame 9a · Desktop draft modal — contact dropdown1200×800 · dark · popover anchored to the Recipient field
The full session transcriptevery message, tool call and diff
Code in the shared roots~/proj/app/cc-pocket ~/proj/app/cc-pocket-protocol
THEY CAN'T DO
Edit or write files
Reach your other sessions or folders
Pass this handoff on to someone else
The full conversation history of this session will be visible — including anything you pasted into it.
Handoff briefautoEdit
Ask
Review the concurrency around the refresh mutex before I open the PR.
Your input locks until Frank returns
The picker is a type-ahead popover anchored to the field, matching the model popover: 340px, raised, hairline, same group labels as mobile. Typing filters; ↑↓ moves; ⏎ selects. The connect row is pinned to the popover footer so the QR path is reachable without abandoning the draft — choosing it opens Frame 3 as a small dialog and returns here. Desktop footer keeps the shipped cost-restatement chip; only the primary verb changed.
Frame 9b · Desktop Settings ▸ Collaborators1200×800 · dark · list + detail, existing two-column settings grammar
Settings
Settings
Daemon & devices
Shared folders
Collaborators
Handoffs
Advanced
Collaborators4 connected
Search…
FFrank Osei⇄connected 12d ago · 3 handoffs
MMei Tanaka⇄connected 4mo ago · 5 handoffs
AAiko Sato→connected 2m ago · no handoffs yetno daemon
DDev Rao⇄connected 2mo ago · 1 handoff
SSofia Lindqvistlink revoked 8d agoremoved
F
Frank Osei ⇄
Frank's Mac mini · connected 12d ago
Safety fingerprint
tiger — brick — mango — void
amber — pilot — nine — harbor
Direction
Mutualyou can send Frank handoffs · Frank can send you handoffsBoth ways
Handoffs with Frank3
Refactor auth module18 min · 5 findings · todayCompleted
Fix flaky socket testpulled back after 4 min · 2d agoRecalled
Release notes 1.63 weeks agoDeclined
Remove connectionFrank can no longer reach you and you can't send him handoffs; past handoffs stay in history.
List left, detail right — the same 340px + fluid split the draft modal uses, so Settings and dialogs share one rhythm. Selection is a raised row, not a tinted one: terracotta stays reserved for needs-you moments. Mutual links collapse to a single row with a Both ways chip rather than two entries; the pane never implies presence — “connected 12d ago” is when the binding was made. Remove gets an explicit button on desktop (no swipe affordance) and states both halves of the consequence for a mutual link.